【ChatGPT】GitHubプルリクエスト作成時に差分を要約、自動コメントする仕組みを作ってみた
ChatGPT の波に乗るべく、私もOpenAI APIを触り始めました。 ChatGPTの用途は様々あります。個人的には、要約機能が特に汎用的だと感じています。 以下のような活用ブログもありますね。
- OpenAIを使ってDevelopersIOの新着記事を3行で要約してSlackに投稿してみた | DevelopersIO
- ChatGPT を使ってちょうど読んでいた論文を3行まとめにしてもらった | DevelopersIO
本ブログでも ChatGPTによる要約スキルを確かめてみます。 ただ、「コンテンツそのものの要約」ではなく 「コンテンツの更新内容の要約」 ができないか、 に焦点を当てます。
今回 ChatGPTには、いわゆる diff
コマンドの結果を解析してもらいます。 そして、それを活用した「GitHubプルリクエスト(PR)の更新内容を要約する仕組み」まで作ってみました。
はじめにまとめ
以下 Pythonスクリプト、およびGitHub Actions ワークフローを作りました。
- summarize_diff.py | Gist : ChatGPTへ「diffコマンドの結果」を渡し、内容を要約するスクリプト
- comment-pr-diff-summary-by-chatgpt.yml | Gist : 上記スクリプトをPR作成時に自動で走らせるためのワークフロー
summarize_diff.py
の入力は 対象コンテンツのパス
と そのコンテンツの更新差分(diffコマンド)
です。 以下実行例です。
$ cat diff.txt # 1c1 # < 突然ですがラスベガスの美味しいラメーン屋さんを紹介します # --- # > 突然ですがラスベガスの美味しいラーメン屋さんを紹介します $ python3 summarize_diff.py blog.md diff.txt # 更新内容は以下のとおりです。 # # - 「ラメーン屋」を「ラーメン屋」に修正しました。
.github/workflows/comment-pr-diff-summary-by-chatgpt.yml
は上記Pythonスクリプトを組み込んだ GitHun Actions ワークフローです。 以下プルリクエスト作成時の動作例です。
作ってみる
[事前準備] OpenAIのAPIキーを発行・登録
OpenAI APIを利用するためのキーを発行します。
そのキーをGitHub のシークレットに登録します。 リポジトリの [Settings > Security > Secrets and variables > Actins] の画面へ移動します。 [Repository secrets] に OPENAI_API_KEY
として登録します。
これで GitHub Actions から OpenAI API を呼び出す準備ができました。
Pythonスクリプトを作成
以下のような Pythonスクリプトを作成しました。
import os import sys import openai from pathlib import Path content_path = sys.argv[1] diff_path = sys.argv[2] # APIキーの設定 openai.api_key = os.environ.get("OPENAI_API_KEY") chatgpt_system_content = '''これから以下の入力を与えます。 - 入力(コンテンツのファイル名) - 入力(「修正前のコンテンツ」と「修正後のコンテンツ」のdiffコマンドの結果) - "< " から始まる行は修正前のコンテンツに該当します - "> " から始まる行は修正後のコンテンツに該当します あなたは与えられた入力から「修正前のコンテンツ」と「修正後のコンテンツ」の差分を調べてください。 「修正前のコンテンツ」と「修正後のコンテンツ」の差分から、修正内容を要約して出力してください。 ### 制約 - Markdown形式で出力してください。 - 出力の先頭は「更新内容は以下のとおりです。」としてください。 - リスト形式で更新内容を出力してください。 - 1つのリストアイテムには1つの更新内容を出力してください。 - 日本語で出力してください。 - "です・ます調" で出力してください。 ''' chatgpt_user_content = "### 入力(コンテンツのファイル名)\n\n" + content_path \ + "\n\n### 入力(「修正前のコンテンツ」と「修正後のコンテンツ」のdiffコマンドの結果)\n\n" + Path(diff_path).read_text() response = openai.ChatCompletion.create( model="gpt-3.5-turbo", messages=[ {"role": "system", "content": chatgpt_system_content}, {"role": "user", "content": chatgpt_user_content}, ], ) print(response.choices[0]["message"]["content"].strip())
プロンプトとしては「指示」と「インプット」に分かれます。
「指示」は以下のような内容です。 一言で言うと "これからインプットされるdiffコマンドを調査して要約してね"
です。
これから以下の入力を与えます。 - 入力(コンテンツのファイル名) - 入力(「修正前のコンテンツ」と「修正後のコンテンツ」のdiffコマンドの結果) - "\< " から始まる行は修正前のコンテンツに該当します - "\> " から始まる行は修正後のコンテンツに該当します あなたは与えられた入力から「修正前のコンテンツ」と「修正後のコンテンツ」の差分を調べてください。 「修正前のコンテンツ」と「修正後のコンテンツ」の差分から、修正内容を要約して出力してください。 ### 制約 - Markdown形式で出力してください。 - 出力の先頭は「更新内容は以下のとおりです。」としてください。 - リスト形式で更新内容を出力してください。 - 1つのリストアイテムには1つの更新内容を出力してください。 - 日本語で出力してください。 - "です・ます調" で出力してください。
「インプット」には以下のような形式で貼り付けます。
### 入力(コンテンツのファイル名) ${ファイル名} ### 入力(「修正前のコンテンツ」と「修正後のコンテンツ」のdiffコマンドの結果) ${diffコマンドの結果
以下にローカル環境で試した例を1つ載せます。
$ cat diff.txt # 2c2 # < cidr_block = "10.0.0.0/16" # --- # > cidr_block = "10.0.0.0/24" $ python3 summarize_diff.py network.tf diff.txt # 更新内容は以下のとおりです。 # # - cidr_block を "10.0.0.0/16" から "10.0.0.0/24" に変更しました。
GitHub Actions ワークフローを作成
作ったワークフローは以下のとおり。
name: comment-pr-summary-by-chatgpt on: pull_request_target: types: [ opened, reopened ] jobs: comment-pr-summary-by-chatgpt: runs-on: ubuntu-latest permissions: contents: read pull-requests: write steps: ##### # セットアップ ##### - name: Switch to pull request branch uses: actions/checkout@v3 with: ref: ${{ github.event.pull_request.head.sha }} - name: Fetch base branch run: | git fetch origin \ ${{ github.event.pull_request.base.sha }}:BASE - name: Setup Python uses: actions/setup-python@v3 with: python-version: '3.9' architecture: 'x64' - name: Install OpenAI run: | pip install -U openai ##### # 更新されたファイル名一覧を取得 ##### - name: Get diff files run: | # ※特定拡張子にフィルタしています git diff --diff-filter=M --name-only HEAD BASE \ | grep -e "\.md" -e "\.tf$" -e "\.yaml$" -e "\.py$" > _diff_files.txt cat _diff_files.txt ##### # Run ChatGPT ##### - name: run ChatGPT shell: bash {0} env: OPENAI_API_KEY: ${{secrets.OPENAI_API_KEY}} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} URL: ${{ github.event.pull_request.html_url }} run: | # 各ファイルに対して「差分取得 -> 要約 -> PRコメント」を行う cat _diff_files.txt \ | while read file_path;do echo "## summarize updates: ${file_path}" # 修正前テキスト, 修正後テキストの取得 git cat-file -p BASE:${file_path} > _before.txt git cat-file -p HEAD:${file_path} > _after.txt # 差分の取得 diff _before.txt _after.txt > _diff.txt | true cat _diff.txt # ChatGPTによる要約の取得 chatgpt_response=$(python3 summarize_diff.py "${file_path}" "_diff.txt") echo "${chatgpt_response}" # GitHub PR へコメント echo "### :robot: ${file_path} の更新まとめ by ChatGPT ${chatgpt_response}" > _body.txt gh pr comment --body-file _body.txt "${URL}" done
Get diff files
ステップで PRの Headブランチと Baseブランチの差分(変更差分のみ)を取得します。
それぞれのファイルに対して run ChatGPT
ステップで要約を取得しています。 以下、実行している bash スクリプトです。
# 各ファイルに対して「差分取得 -> 要約 -> PRコメント」を行う cat _diff_files.txt \ | while read file_path;do echo "## summarize updates: ${file_path}" # 修正前テキスト, 修正後テキストの取得 git cat-file -p BASE:${file_path} > _before.txt git cat-file -p HEAD:${file_path} > _after.txt # 差分の取得 diff _before.txt _after.txt > _diff.txt | true cat _diff.txt # ChatGPTによる要約の取得 chatgpt_response=$(python3 summarize_diff.py "${file_path}" "_diff.txt") echo "${chatgpt_response}" # GitHub PR へコメント echo "### :robot: ${file_path} の更新まとめ by ChatGPT ${chatgpt_response}" > _body.txt gh pr comment --body-file _body.txt "${URL}" done
試してみる
Pythonコードの修正
「不完全なFizzBuzzコード」を完成させるPRを作ります。
返ってきたコメントがこちら。
ファイル名( test/fizzbuzz.py
)や print("FizzBuzz")
という情報から 『3の倍数と5の倍数』 と、よしなに予測していそうです。
ただ、再度実行するとこんな感じで、とても機械的な差分出力になりました。少し残念。
CloudFormationテンプレートの修正
CloudFormationテンプレートのパラメータを修正してみます。
結果がこちら。必要最小限ですが悪くないです。
Terraformコードの修正
パラメータ変更とリソース削除のPRを出してみます。
返ってきたコメントがこちら。
いいですね。削除された行の塊を「aws_flow_logリソース」と把握してくれているのは 地味にGoodポイントではないでしょうか。
Markdownファイル更新
「タイポ修正を3つ」と「加筆」を行っています。
返ってきたコメントがこちら。
うーん。言っていることは合っているけど、もうちょっと修正したポイントにフォーカスしてほしいですね。
追加で何度か試してみました。3回目の「誤り(=タイポ修正)」という表現はGoodですね。
考慮点や課題など
ワークフロー・スクリプトの考慮点
今回作った仕組みは差分のあるファイル毎に OpenAI API を叩きます。 大量のファイル更新の差分があるPRを作成する際には、 実行時間およびAPI利用料金には注意してください。
また、簡易的なPythonスクリプトなので「トークン数上限」考慮を入れていません。 以下のように事前にトークン数を見積るツールを適宜活用すると良さそうです。
品質向上の課題
本当は diffコマンドだけではなく、 以下のように「修正前」と「修正後」のコンテンツも参考情報としてインプットさせたかったです。
### 入力(コンテンツのファイル名) ${ファイル名} ### 入力(修正前のコンテンツ) ${修正前のコンテンツ} ### 入力(修正後のコンテンツ) ${修正後のコンテンツ} ### 入力(「修正前のコンテンツ」と「修正後のコンテンツ」のdiffコマンドの結果) ${diffコマンドの結果}
…が先程のトークン数上限の関係上、難しかったです。
「どの括り」の「どの部分」が変わったかが分かるのはメリットなので、 上限が無ければ追加しちゃいたいですね。
以下、「修正前」と「修正後」のコンテンツもインプットさせて、成功したときのキャプチャです。
- VPCの CIDRブロックが…
- VPCの DNSサポートが…
というように、どのリソースのどのパラメータが変わったかを、ChatGPTが判断してくれています。
おわりに
以上、OpenAI API をプルリクエストに組み込んでみました。
今回の出来としては、「あくまで参考」程度に捉えるのが良いのかなと思いますが、 ざっくりと変更点を日本語で書いてくれるのは良いですね。
プロンプトもまだまだ改善できそうなので、チューニングしていきたいです。
参考
- <初心者向き> OpenAI APIを使ってPythonでChatGPT遊びするための最初の三歩くらい | DevelopersIO
- OpenAI Platformことはじめ 〜Organizationメンバーに招待されたら | DevelopersIO
- Webhook events and payloads - GitHub Docs
補足
「Pythonコードの修正」テストの diffコマンドの結果
2c2,4 < if i % 3 == 0: --- > if i % 15 == 0: > print("FizzBuzz") > elif i % 3 == 0:
「CloudFormationテンプレートの修正」テストの diffコマンドの結果
2c2 < cidr_block = "10.0.0.0/24" --- > cidr_block = "10.0.0.0/16" 4c4 < enable_dns_support = false --- > enable_dns_support = true
「Terraformコードの修正」テストの diffコマンドの結果
2c2 < cidr_block = "10.0.0.0/24" --- > cidr_block = "10.0.0.0/16" 4c4 < enable_dns_support = false --- > enable_dns_support = true 16,27d15 < } < } < < resource "aws_flow_log" "this" { < log_destination = local.flowlogs_bucket_arn < log_destination_type = "s3" < traffic_type = "ALL" < vpc_id = aws_vpc.this.id < destination_options { < file_format = "parquet" < hive_compatible_partitions = true < per_hour_partition = true
「Markdownファイル更新」テストの diffコマンドの結果
1c1 < そもそもマルチアカウンt戦略とは --- > そもそもマルチアカウント戦略とは 7c7 < そもそもAWS(クラウド)を使うこと辞退が、アジリティとガバナンスを両立できるための手段となります。 --- > そもそもAWS(クラウド)を使うこと自体が、アジリティとガバナンスを両立できるための手段となります。 14c14 < - リソースのセキュリティ評価 ( by AWS SecurityHub, Amazon Inspector ) など... --- > - リソースのセキュリティ評価 ( by AWS Security Hub, Amazon Inspector ) など... 22a23,28 > > 具体的なマルチアカウント戦略のメリットは以下のとおりです。 > > - メリット1: セキュリティ向上 > - メリット2: 開発スピードの促進 > - メリット3: コスト最適化